import RoadNode from "./RoadNode";
import IRoadSeeker from "./IRoadSeeker";

/**
 * A*寻路算法 
 * @author 落日故人 QQ 583051842
 * 
 */
export default class AStarRoadSeeker implements IRoadSeeker {

    
    /**
     * 横向移动一个格子的代价
     */		
    private COST_STRAIGHT:number = 10; 
    
    /**
     * 斜向移动一个格子的代价
     */		
    private COST_DIAGONAL:number = 14;
    
    /**
     *最大搜寻步骤数，超过这个值时表示找不到目标 
        */		
    private maxStep:number = 1000;
    
    /**
     * 开启列表
     */		
    private _openlist:Array<RoadNode>; 
    
    /**
     *关闭列表 
        */		
    private _closelist:Array<RoadNode>;
    
    /**
     *开始节点 
        */		
    private _startNode:RoadNode;
    
    /**
     *当前检索节点 
        */		
    private _currentNode:RoadNode;
    
    /**
     *目标节点 
        */		
    private _targetNode:RoadNode;
    
    /**
     *地图路点数据 
        */		
    private _roadNodes:{[key:number]:RoadNode};
    
    /**
     *用于检索一个节点周围8个点的向量数组 
        */		
    private _round:number[][] = [[0,-1],[1,-1],[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1]]
    
    private handle:number = -1;

    /**
     * 是否优化路径
     */
    private optimize:boolean = true;
    
    public constructor(roadNodes:{[key:string]:RoadNode})
    {
        this._roadNodes = roadNodes;
    }
    
    
    /**
     *寻路入口方法 
        * @param startNode
        * @param targetNode
        * @return 
        * 
        */		
    public seekPath(startNode:RoadNode,targetNode:RoadNode):Array<RoadNode>
    {
        this._startNode = startNode;
        this._currentNode = startNode;
        this._targetNode = targetNode;
        
        if(!this._startNode || !this._targetNode)
            return [];
        
        if(this._targetNode.value == 1)
        {
            console.log("目标不可达到：");
            return [];
        }
        
        this._openlist = [];
        this._closelist = [];
        
        var step:number = 0;
        
        while(true)
        {
            if(step > this.maxStep)
            {
                console.log("没找到目标计算步骤为：",step);
                return [];
            }
            
            step++;
            
            this.searchRoundNodes(this._currentNode);
            
            if(this._openlist.length == 0)
            {
                console.log("没找到目标计算步骤为：",step);
                return [];
            }
            
            this._openlist.sort(this.sortNode);
            
            this._currentNode = this._openlist.shift();
            
            if(this._currentNode == this._targetNode)
            {
                console.log("找到目标计算步骤为：",step);
                return this.getPath();
            }else
            {
                this._closelist.push(this._currentNode);
            }
            
        }
        
        return [];
    }


    /**
     *寻路入口方法 如果没有寻到目标，则返回离目标最近的路径
    * @param startNode
    * @param targetNode
    * @return 
    * 
    */		
    public seekPath2(startNode:RoadNode,targetNode:RoadNode):Array<RoadNode>
    {
        this._startNode = startNode;
        this._currentNode = startNode;
        this._targetNode = targetNode;
        
        if(!this._startNode || !this._targetNode)
            return [];
        
        /*if(this._targetNode.value == 1)
        {
            console.log("目标不可达到：");
            return [];
        }*/
        
        this._openlist = [];
        this._closelist = [];
        
        var step:number = 0;
        
        var closestNode:RoadNode = null; //距离目标最近的路点

        while(true)
        {
            if(step > this.maxStep)
            {
                console.log("没找到目标计算步骤为：",step);
                return this.seekPath(startNode,closestNode);
            }
            
            step++;
            
            this.searchRoundNodes(this._currentNode);
            
            if(this._openlist.length == 0)
            {
                console.log("没找到目标计算步骤为：",step);
                return this.seekPath(startNode,closestNode);
            }
            
            this._openlist.sort(this.sortNode);
            
            this._currentNode = this._openlist.shift();

            if(closestNode == null)
            {
                closestNode = this._currentNode;
            }else
            {
                if(this._currentNode.h < closestNode.h)
                {
                    closestNode = this._currentNode;
                }
            }
            
            if(this._currentNode == this._targetNode)
            {
                console.log("找到目标计算步骤为：",step);
                return this.getPath();
            }else
            {
                this._closelist.push(this._currentNode);
            }
            
        }
        
        return this.seekPath(startNode,closestNode);
    }
    
    /**
     * 对路节点进行排序
     * @param node1 
     * @param node2 
     */
    private sortNode(node1:RoadNode,node2:RoadNode)
    {
        if(node1.f < node2.f)
        {
            return -1;
        }else if(node1.f > node2.f)
        {
            return 1;
        }

        return 0;
    }

    /**
     *获得最终寻路到的所有路点 
        * @return 
        * 
        */		
    private getPath():Array<RoadNode>
    {
        var nodeArr:Array<RoadNode> = [];
        
        var node:RoadNode = this._targetNode;
        
        while(node != this._startNode)
        {
            nodeArr.unshift(node);
            node = node.parent;
        }
        
        nodeArr.unshift(this._startNode);

        if(this.optimize)
        {
            return nodeArr;
        }
        
        //把多个节点连在一起的，横向或者斜向的一连串点，除两边的点保留
        for(var i:number = 1 ; i < nodeArr.length - 1 ; i++)
        {
            var preNode:RoadNode = nodeArr[i - 1] as RoadNode;
            var midNode:RoadNode = nodeArr[i] as RoadNode;
            var nextNode:RoadNode = nodeArr[i + 1] as RoadNode;
        
            var bool1:Boolean = midNode.cx == preNode.cx && midNode.cx == nextNode.cx;
            var bool2:Boolean = midNode.cy == preNode.cy && midNode.cy == nextNode.cy;
            var bool3:Boolean = ((midNode.cx - preNode.cx) / (midNode.cy - preNode.cy)) * ((nextNode.cx - midNode.cx) / (nextNode.cy - midNode.cy)) == 1
        
            if(bool1 || bool2 || bool3)
            {
                nodeArr.splice(i,1)
                i--;
            }
        }
        
        return nodeArr
    }
    
    /**
     *测试寻路步骤 
        * @param startNode
        * @param targetNode
        * @return 
        * 
        */		
    public testSeekPathStep(startNode:RoadNode,targetNode:RoadNode,callback:Function,target:any,time:number = 100):void
    {
        this._startNode = startNode;
        this._currentNode = startNode;
        this._targetNode = targetNode;
        
        if(this._targetNode.value == 1)
            return;
        
        this._openlist = [];
        this._closelist = [];
        
        var step:number = 0;
        
        clearInterval(this.handle);
        this.handle = setInterval(()=>
        {
            if(step > this.maxStep)
            {
                console.log("没找到目标计算步骤为：",step);
                clearInterval(this.handle);
                return;
            }
            
            step++;
            
            this.searchRoundNodes(this._currentNode);
            
            if(this._openlist.length == 0)
            {
                console.log("没找到目标计算步骤为：",step);
                clearInterval(this.handle);
                return;
            }
            
            this._openlist.sort(this.sortNode);
            
            this._currentNode = this._openlist.shift();
            
            if(this._currentNode == this._targetNode)
            {
                console.log("找到目标计算步骤为：",step);
                clearInterval(this.handle);
                callback.apply(target,[this._startNode,this._targetNode,this._currentNode,this._openlist,this._closelist,this.getPath()]);
            }else
            {
                this._closelist.push(this._currentNode);
                callback.apply(target,[this._startNode,this._targetNode,this._currentNode,this._openlist,this._closelist,null]);
            }

        },time);
        
    }
    
    /**
     *查找一个节点周围可通过的点 
        * @param node
        * @return 
        * 
        */		
    private searchRoundNodes(node:RoadNode):void
    {
        for(var i:number = 0 ; i < this._round.length ; i++)
        {
            var cx:number = node.cx + this._round[i][0];
            var cy:number = node.cy + this._round[i][1];
            var node2:RoadNode = this._roadNodes[cx + "_" + cy] as RoadNode
            
            if(node2 != null && node2 != this._startNode && node2.value != 1 && !this.isInCloseList(node2) && !this.inInCorner(node2))
            {
                this.setNodeF(node2);
            }
        }
    }
    
    /**
     *设置节点的F值 
        * @param node
        * 
        */		
    public setNodeF(node:RoadNode):void
    {	
        var g:number;
        
        if(node.cx == this._currentNode.cx || node.cy == this._currentNode.cy)
        {
            g = this._currentNode.g + this.COST_STRAIGHT;
        }else
        {
            g = this._currentNode.g + this.COST_DIAGONAL;
        }
        
        if(this.isInOpenList(node))
        {
            if(g < node.g)
            {
                node.g = g;
                
            }else
            {
                return;
            }
        }else
        {
            node.g = g;
            this._openlist.push(node);
        }
        
        node.parent = this._currentNode;
        node.h = (Math.abs(this._targetNode.cx - node.cx) + Math.abs(this._targetNode.cy - node.cy)) * this.COST_STRAIGHT;
        node.f = node.g + node.h;
    }
    
    /**
     *节点是否在开启列表 
        * @param node
        * @return 
        * 
        */		
    private  isInOpenList(node:RoadNode):Boolean
    {
        return this._openlist.indexOf(node) != -1 ? true : false;
    }
    
    /**
     *节点是否在关闭列表 
        * 
        */		
    private  isInCloseList(node:RoadNode):Boolean
    {
        return this._closelist.indexOf(node) != -1 ? true : false;
    }
    
    /**
     *节点是否在拐角处 
        * @return 
        * 
        */		
    private  inInCorner(node:RoadNode):Boolean
    {
        if(node.cx == this._currentNode.cx || node.cy == this._currentNode.cy)
        {
            return false;
        }
        
        var node1:RoadNode = this._roadNodes[this._currentNode.cx + "_" + node.cy] as RoadNode;
        var node2:RoadNode = this._roadNodes[node.cx + "_" + this._currentNode.cy] as RoadNode;
        
        if(node1 != null && node1.value == 0 && node2 != null && node2.value == 0)
        {
            return false;
        }
        
        return true;
    }
    
    public  dispose():void
    {
        this._roadNodes = null;
        this._round = null;
    }
}
